package app.model.flare.proxies
{
	import app.model.flare.vos.ActionVO;
	import app.model.flare.vos.StateVO;
	
	import com.adobe.serialization.json.JSON;
	
	import flare.vis.data.Data;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.data.Tree;
	
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	
	import mx.collections.ArrayCollection;
	import mx.collections.IList;
	
	import org.puremvc.as3.multicore.patterns.proxy.Proxy;

	public class FlareDataProxy extends Proxy
	{
		public static const NAME:String = "FlareDataProxy";
		public static const FLARE_DATA_POPULATED:String = "flareData/populated";
		public static const DATA_FLUSHED:String = "flareData/dataFlushed";
		public static const DATA_PARSED:String = "flareData/dataParsed";
		
		
		
		private var url:String;
		private var map:Object;
		private var stateMap:ArrayCollection;
		private var actionMap:ArrayCollection;
		public var autoUpdate:Boolean = true;;
		public function FlareDataProxy(url:String = null):void
		{
			this.url = url;
			super(NAME);
		}
		
		private function get source():Data{
			return Data( data );
		}
		
		public function addState( name:String, pkg:String ):Boolean{
			var d:StateVO;
			if(containsState( name ))return false;
			if(!pkg){
				pkg = "root.";
			}else{
				pkg = "root." + pkg +".";
			}
			d = new StateVO();
			d.fullPath = pkg + name;
			stateMap.addItem( d );
			updateVisualisation();
			return true;
		}
		
		public function get stateList():IList{
			return stateMap;
		}
		public function get actionList():IList{
			return actionMap;
		}
		
		public function removeState( name:String, update:Boolean=true ):Boolean{
			var state:StateVO,
				index:int;
			if(!containsState( name ))return false;
			state = getState( name );
			index = stateMap.getItemIndex( state );
			stateMap.removeItemAt( index );
			removeIncomingActions( state );
			updateVisualisation();
			
			
			return true;
		}
		
		private function removeIncomingActions( state:StateVO ):void{
			var action:ActionVO,
				vo:StateVO;
			for each (action in actionMap ){
				if(action.target.name == state.name)
					removeActionInternal(action.source.name, action.name);			
			}
		}
		
		
		
		
		private function sendDataFlushed():void{
			sendNotification(DATA_FLUSHED);
		}
		
		private function sendDataParsed():void{
			sendNotification(DATA_PARSED);
		}
		
		public function addAction( fromName:String, toName:String, actionName:String ):Boolean{
			var f:StateVO,
				t:StateVO,
				action:ActionVO;
			if( !containsState( fromName ) || !containsState( toName ) || fromName  == toName )return false;
			f = getState( fromName );
			t = getState( toName );
			action = new ActionVO( actionName, t )
			if( f.contains( action.name ) )return false;
			f.addAction( action );
			actionMap.addItem( action );
			updateVisualisation();
			return true;
		}
		
		public function removeAction( fromName:String, actionName:String ):Boolean{
			var success:Boolean = removeActionInternal(fromName,actionName)
			if(success)updateVisualisation();
			return success;
		}
		
		private function removeActionInternal( fromName:String, actionName:String ):Boolean{
			var f:StateVO,
				action:ActionVO,
				index:int;
			if( !containsState( fromName ) )return false;
			f = getState( fromName );
			if( !f.contains( actionName ) )return false;
			f.removeAction( actionName );
			index = actionMap.getItemIndex( action )
			if(index != -1)actionMap.removeItemAt( index );
			return true;
		}
		
		
		
		public function update():void{
			setData( buildData(stateMap) );
		}
		
		public function flush(notify:Boolean=true):void{
			map = {};
			stateMap.source = [];
			actionMap.source = [];
			if(notify)sendDataFlushed();
		}
		
		private function updateVisualisation():void{
			if( stateMap.length == 0 ){
				flush();
				sendDataFlushed();
			}else if(autoUpdate){
				update(  );
			}
		}
		
		public function getState( name:String ):StateVO{
			var n:int,
				vo:StateVO;
			while( n < stateMap.length ){
				vo = stateMap[n];
				if(vo.name == name)return vo;
				n++;
			}
			return null;
		}
		
		
		public function containsState( name:String ):Boolean{
			var n:int,
				vo:StateVO;
			while( n < stateMap.length ){
				vo = stateMap[n];
				if(vo.name == name)
					return true;
				n++;
			}
			return false;
		}
		
		override public function setData( data:Object ):void{
			super.data = data;
			sendNotification( FLARE_DATA_POPULATED, getData() );
		}
		
		public function aquire():void{
			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onLoadComplete);
			loader.load(new URLRequest(url));
		}
		
		private function onLoadComplete(event:Event):void{
			var a:Array,
				loader:URLLoader,
				d:Data;
			loader = URLLoader( event.target );
			a = JSON.decode(loader.data as String) as Array;
			stateMap = parseData(a)
			sendDataParsed();
			update();
		}
		
		public function get packageList():Array{
			var a:Array = [],
				state:StateVO,
				index:int;
			for each (state in stateMap ){
				index = a.indexOf( state.pkg )
				if(index == -1 )a.push(state.pkg);
			}
			return a;
		}
		
		
		
		public function parseData(tuples:Array):ArrayCollection{
			var a:ArrayCollection,
				state:StateVO,
				o:Object;
			a = new ArrayCollection();
			for each (o in tuples) {
				a.addItem( new StateVO ( o ) );
			}
			
			inseminate(a);
			
			return a;
		}
		
		private  function inseminate(states:ArrayCollection):void{
			var state:StateVO;
			for each (state in states) {
				state.inseminate(states.source);
			}
		}
		
		public function buildData(tuples:ArrayCollection):Data
		{
			var fdata:Data = new Data();
			var tree:Tree = new Tree();
			map ={};
			
			tree.root = fdata.addNode({name:"root", size:0});
			map.root = tree.root;
			
			var t:StateVO, u:NodeSprite, v:NodeSprite;
			var p:String, pp:String, i:uint;
			
			// build data set and tree edges
			//tuples.sortOn("fullPath");
			for each (t in tuples) {
				addTuple(t, fdata, tree)
			}
			
			actionMap = createActions(tuples, fdata);
			
			
			
			
			for each (u in tree.nodes) {
				u.sortEdgesBy(NodeSprite.CHILD_LINKS, "target.data.order");
			}
			
			fdata.tree = tree;
			return fdata;
		}
		
		private function createActions(tuples:ArrayCollection, fdata:Data):ArrayCollection{
			// create graph links
			var t:StateVO,
				a:ArrayCollection,
				sourceNode:NodeSprite,
				targetNode:NodeSprite,
				action:ActionVO,
				actionEdge:EdgeSprite;
			a=new ArrayCollection();
			for each (t in tuples) {
				sourceNode = map[t.fullPath];
				for each ( action in t.targets) {
					targetNode = map[action.target.fullPath];
					if (targetNode){
						actionEdge = fdata.addEdgeFor(sourceNode, targetNode, true);
						actionEdge.data = action;
						a.addItem( action );
					} 
					else trace ("Missing node: "+action.name);
				}
			}
			return a;
		}
		
		private function addTuple(t:StateVO, fdata:Data, tree:Tree):void{
			var u:NodeSprite;
			var path:Array, p:String;
			
			p = addGroupToTree(fdata, tree, t.pkgArray);
				
			u = fdata.addNode(t);
			tree.addChild(map[p], u);
			map[t.fullPath] = u;
		}
		
		private function addGroupToTree(fdata:Data, tree:Tree, path:Array):String{
			var u:NodeSprite;
			var p:String, pp:String, i:uint;
			for (i=0, p=""; i<path.length; ++i) {
					pp = p;
					p += (i?".":"") + path[i];
					if (!map[p]) {
						u = fdata.addNode({name:p, size:0});
						tree.addChild(map[pp], u);
						map[p] = u;
					}
				}
				return p
			}
			
	}
	
	
}